package shaw.wifi.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnManagerPNames;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;

import shaw.wifi.Constants;
import shaw.wifi.NetworkSettingsTaskManager;
import shaw.wifi.gui.SecureConnectGui;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.util.Log;

/* This class is responsible for sending information back and forth to the webserver
 * which will tell us the login information. It stores the login information in the
 * NetworkSettings object, and once the comunication between the app and the server
 * has finished, it tells the NetworkSettings object to update the phone's networks
 * with the new network.
 */

/*			COMMUNICATION WITH THE SERVER:
 * 	App										Server
 * 	Send Login and get wifi info--------------->
 * 
 * 										Authenticate user
 * 	 <----------------------------------Send new wifi info
 * 
 * 	process wifi info
 * 	send login and updating message------------>
 * 
 * 										authenticate user
 * 	<-----------------------------------Send ok message
 *  update wifi settings				
 * 
 * 
 * The network settings are updated in 5 steps. First the app requests a new set
 * of network information from the server. The server responds with network information
 * and the information is processed and stored in a NetworkSettings object. The app
 * tells the server it got the information and is updating its networks. The server
 * tells the app that it knows, and then the application updates the phone's wifi settings.
 */

/* This is an asyncronous task, meaning it runs in the background as the android app is
 * running. 
 */

public class NetworkSettingsTask extends AsyncNetworkTask {
	//login information
	//Note that the hostname is on the app right now for testing purposes. The user would
	//not need to enter a hostname if the app could be hardcoded to always talk to a
	//specific host
	private String username;
	private String password;
	
	//an object to store and update the network settings
	private NetworkSettings networkSettings = new NetworkSettings();
	
	//the WifiManager for the system
	private WifiManager wifi;
	
	//the current context
	private Context ctx;
	
	private boolean disableOpen = false;

	@Override
	protected Integer doInBackground(ConnectionInformation... params) {
		if(params[0] == null) {
			return null;
		} else {
			//setup the NetworkSettingsTask local variables
			username = params[0].username;
			password = params[0].password;
			
			disableOpen = params[0].disableShawOpen;
			
			ctx = NetworkSettingsTaskManager.get().getApplicationContext();
			wifi = (WifiManager) NetworkSettingsTaskManager.get().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
			
			//call the main function, getting the network settings, updating with the server, and settign the settings
			getUpdateSet();
		}
		
		Log.d("TASK", "Finished...");
		
		return null;
	}
	
	public void getUpdateSet() {		
		boolean success = sendGetNetworkSettingsRequest();
		if(success == false) {
			return;
		}
		success = sendUpdateNetworkSettingsRequest();
		if(success == false) {
			return;
		}
		applyNetworkSettings();
		
		//set as finished (including disable the button)
		Constants._FINISHED = true;
	}
	
	private boolean sendGetNetworkSettingsRequest() {
		//set some parameters for the http connection. Here we set the connection timeout to
		//4 seconds and the socket will time out at 6 seconds
		HttpParams httpParams = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParams, Constants._HTTPTIMEOUT*1000);
		HttpConnectionParams.setSoTimeout(httpParams, Constants._SOCKETTIMEOUT*1000);
		
		//set new params (USE EXPECT CONTINUE may do something)
		httpParams.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
		httpParams.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
		httpParams.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
		HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
		
		//create a CertHttpClient for the requests
		HttpClient httpClient = new CertHttpClient(ctx, httpParams);
		//create a POST request to hostname + /getsetupdate.php. This will look like
		//https://123.456.789.123/getsetupdate.php
		HttpPost post = new HttpPost(Constants._HOSTNAME + Constants._APPSCRIPTPATH);
		
		publishProgress("Connecting to " + Constants._HOSTNAME + "...");
		        		
		try {
			//values to the POST request so that the server can authenticate the
			//user (username and password) and know what to do with the request (action)
			List<NameValuePair> data = new ArrayList<NameValuePair> (3);
			data.add(new BasicNameValuePair("username", username));
			data.add(new BasicNameValuePair("password", password));
			data.add(new BasicNameValuePair("action", "getpeap"));
		
			//encode the data in the post request
			post.setEntity(new UrlEncodedFormEntity(data));
			
			//execute the POST request
			//this is sychronous so it waits for the response to be recieved
			HttpResponse resp = httpClient.execute(post);
			
			//hande the response
			boolean handleResp = handleNetworkSettingsResponse(resp);
			
			//return how the response was handled
			return handleResp;
		} catch (SocketTimeoutException e) {
			//timeout
			
			e.printStackTrace();
			Log.e("TASK", "Timeout...");
			publishProgress("Cannot connect to server (connection timed out...)");
			return false;
		} catch (Exception e) {
			//Tell the user there was some kind of error. This is most likely going
			//to be caused by the http request not being sent because there is no
			//connection to the server
			
			e.printStackTrace();
			Log.e("TASK", "Timeout...");
			publishProgress("Cannot connect to server...");
			return false;
		}
	}
	
	private boolean sendUpdateNetworkSettingsRequest() {
		publishProgress("Sending confirmation to server...");
		
		//again, set up some parameters
		HttpParams httpParams = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParams, Constants._HTTPTIMEOUT*1000);
		HttpConnectionParams.setSoTimeout(httpParams, Constants._SOCKETTIMEOUT*1000);
		
		//setup an httpclient to send a post request
		HttpClient httpClient = new CertHttpClient(ctx, httpParams);
		HttpPost post = new HttpPost(Constants._HOSTNAME + Constants._APPSCRIPTPATH);
		
		try {
			//add the appropriate values to the request
			List<NameValuePair> data = new ArrayList<NameValuePair> (4);
			data.add(new BasicNameValuePair("username", username));
			data.add(new BasicNameValuePair("password", password));
			data.add(new BasicNameValuePair("action", "setpeap"));
			data.add(new BasicNameValuePair("rad_user", networkSettings.peapUsername));
			data.add(new BasicNameValuePair("rad_pass", networkSettings.peapPassword));
			
			//encode the values into the request
			post.setEntity(new UrlEncodedFormEntity(data));
						
			//send request
			HttpResponse resp = httpClient.execute(post);
			return handleUpdateResponse(resp);
		} catch (SocketTimeoutException e) {
			//timeout
			
			e.printStackTrace();
			publishProgress("Failed to update server (connection timed out...)");
			return false;
		} catch (IOException e) {
			//an error, again this is likely caused by the request not being able
			//to reach the server
			
			e.printStackTrace();
			publishProgress("Failed to update server...");
			return false;
		}
	}
	
	private boolean handleNetworkSettingsResponse(HttpResponse response) {    	
    	//handle the response from the server which details the new network information
		try {
			//response recieved, don't interrupt process now!
			interruptable = false;
			
			//create a buffered reader to read the individual lines of data off the http response's body
    		BufferedReader br  = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
			//a line to read into
    		String line = "";
    		
    		//read all the lines from the response's body
			while((line = br.readLine()) != null) {
				Log.w("hr1", "recieved from host " + line);
				
				//check for bad login information
				if(line.equals("LOGIN>>false")) {
					publishProgress("Incorrect Login Information");
					return false;
				}
				
				//check for network ssid
				if(line.startsWith("NETWORK>>")) {
					networkSettings.ssid = line.substring(9);
				}
				//check for authentication type peap
				if(line.startsWith("AUTHTYPE>>")) {
					if(line.equals("AUTHTYPE>>basic_peap")) {
						networkSettings.wifiType = NetworkSettings.WIFI_PEAP;
					}
				}
				//check for peap username
				if(line.startsWith("USERNAME>>")) {
					networkSettings.peapUsername = line.substring(10);
				}
				//check for peap password
				if(line.startsWith("PASSWORD>>")) {
					networkSettings.peapPassword = line.substring(10);
				}
				
				//check for peap password
				if(line.startsWith("HIDDEN>>")) {
					if(line.equals("HIDDEN>>true")) {
						networkSettings.hidden = true;
					} else {
						networkSettings.hidden = false;
					}
				}
			}
		} catch (IOException e) {
			//this will not likely occur, and would probably be the result
			//of the buffered reader or input stream reader not being instatiated properly
			//for some reason
			
			publishProgress("Cannot read valid data from server...\n" +
				"Please Ensure you have the most recent version of the ShawSecureConnect application");
			return false;
		}
    	
		//check to make sure that there is enough information to create/update the network settings
		//in the future (WEP) this will be handed by the NetworkSettings class as it updates the network
		//settings as the required information will vary substantially in complexity based on what
		//kind of authentication protocol is utilized
    	if(		networkSettings.wifiType != NetworkSettings.WIFI_PEAP || 
    			networkSettings.ssid == null || 
    			networkSettings.peapUsername == null || 
    			networkSettings.peapPassword == null) {
    		Log.e("rh1", networkSettings.ssid + " " + networkSettings.wifiType + " " + networkSettings.ssid + " " + networkSettings.peapUsername + " " + networkSettings.peapPassword);
    		publishProgress("Cannot read valid data from server...\n" +
    				"Please Ensure you have the most recent version of the ShawSecureConnect application");
    		return false;
    	} else {
    		if(Constants._SHOWSTUFF) {
    			publishProgress("Got network settings from server:");
    			publishProgress("   SSID: " + networkSettings.ssid);
    			publishProgress("   Access Type: " + networkSettings.wifiType);
    			publishProgress("   Username: " + networkSettings.peapUsername);
    			publishProgress("   Password: " + networkSettings.peapPassword);
    		}
    	}
    	
    	return true;
	}
	
	private boolean handleUpdateResponse(HttpResponse response) {
		//handle the second response from the server
		try {
			//read data from the response
			BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
			String line = "";
			while((line = br.readLine()) != null) {
				Log.d("hr2", "recieved " + line);
				//check for update line
				if(line.startsWith("UPDATE>>")) {
					if(line.equals("UPDATE>>false")) {
						//update with server has failed, (wpa-key not confirmed)
						//do not update the network settings
						publishProgress("Failed to update server, try again later...");
						return false;
					} else {
						//update success, procceed
						return true;
					}
				}
			}
		} catch (IOException e) {
			publishProgress("Cannot read from response...");
			return false;
		}
    	
		//response did not contain UPDATE line
		publishProgress("Failed to update server, try again later...");
    	return false;
	}
	
	private boolean applyNetworkSettings() {
		//cannot interrupt the task now
		if(this.isCancelled() == true) {
			return false;
		}
		interruptable = false;
		
		
		//apply the network settings, and check if the application was successful
    	boolean r = networkSettings.apply(wifi, disableOpen);
    	if(r == true) {
    		//settings applied successfully, inform user
    		publishProgress("\nUpdated settings successfully");
    	} else {
    		//apply settings failed, inform user
    		publishProgress("\nFailed to update settings");
    	}
    	
		return r;
	}
	
}
